/*
 * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * DOC:wlan_cp_stats_mc_tgt_api.c
 *
 * This file provide API definitions to update control plane statistics received
 * from southbound interface
 */

#include "wlan_cp_stats_mc_defs.h"
#include "target_if_cp_stats.h"
#include "wlan_cp_stats_tgt_api.h"
#include "wlan_cp_stats_mc_tgt_api.h"
#include <wlan_cp_stats_mc_ucfg_api.h>
#include <wlan_cp_stats_utils_api.h>
#include "../../core/src/wlan_cp_stats_defs.h"

static bool tgt_mc_cp_stats_is_last_event(struct stats_event *ev)
{
	bool is_last_event;

	if (IS_MSB_SET(ev->last_event)) {
		is_last_event = IS_LSB_SET(ev->last_event);
		cp_stats_debug("is_last_event %d", is_last_event);
	} else {
		cp_stats_debug("FW does not support last event bit");
		is_last_event = !!ev->peer_stats;
	}
	return is_last_event;
}

void tgt_cp_stats_register_rx_ops(struct wlan_lmac_if_rx_ops *rx_ops)
{
	rx_ops->cp_stats_rx_ops.process_stats_event =
					tgt_mc_cp_stats_process_stats_event;
}

static void tgt_mc_cp_stats_extract_tx_power(struct wlan_objmgr_psoc *psoc,
					struct stats_event *ev,
					bool is_station_stats)
{
	int32_t max_pwr;
	uint8_t pdev_id;
	QDF_STATUS status;
	struct wlan_objmgr_pdev *pdev;
	struct request_info last_req = {0};
	struct wlan_objmgr_vdev *vdev = NULL;
	struct pdev_mc_cp_stats *pdev_mc_stats;
	struct pdev_cp_stats *pdev_cp_stats_priv;

	if (!ev->pdev_stats)
		return;

	if (is_station_stats)
		status = ucfg_mc_cp_stats_get_pending_req(psoc,
					TYPE_STATION_STATS, &last_req);
	else
		status = ucfg_mc_cp_stats_get_pending_req(psoc,
					TYPE_CONNECTION_TX_POWER, &last_req);

	if (QDF_IS_STATUS_ERROR(status)) {
		cp_stats_err("ucfg_mc_cp_stats_get_pending_req failed");
		goto end;
	}

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, last_req.vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev is null");
		goto end;
	}

	pdev = wlan_vdev_get_pdev(vdev);
	if (!pdev) {
		cp_stats_err("pdev is null");
		goto end;
	}

	pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev);
	if (pdev_id >= ev->num_pdev_stats) {
		cp_stats_err("pdev_id: %d invalid", pdev_id);
		goto end;
	}

	pdev_cp_stats_priv = wlan_cp_stats_get_pdev_stats_obj(pdev);
	if (!pdev_cp_stats_priv) {
		cp_stats_err("pdev_cp_stats_priv is null");
		goto end;
	}

	wlan_cp_stats_pdev_obj_lock(pdev_cp_stats_priv);
	pdev_mc_stats = pdev_cp_stats_priv->pdev_stats;
	max_pwr = pdev_mc_stats->max_pwr = ev->pdev_stats[pdev_id].max_pwr;
	wlan_cp_stats_pdev_obj_unlock(pdev_cp_stats_priv);

	if (is_station_stats)
		goto end;

	ucfg_mc_cp_stats_reset_pending_req(psoc, TYPE_CONNECTION_TX_POWER);
	if (last_req.u.get_tx_power_cb)
		last_req.u.get_tx_power_cb(max_pwr, last_req.cookie);

end:
	if (vdev)
		wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
}

static void peer_rssi_iterator(struct wlan_objmgr_pdev *pdev,
			       void *peer, void *arg)
{
	struct stats_event *ev;
	struct peer_mc_cp_stats *peer_mc_stats;
	struct peer_cp_stats *peer_cp_stats_priv;

	if (WLAN_PEER_SELF == wlan_peer_get_peer_type(peer)) {
		cp_stats_debug("ignore self peer: %pM",
			       wlan_peer_get_macaddr(peer));
		return;
	}

	peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
	if (!peer_cp_stats_priv) {
		cp_stats_err("peer cp stats object is null");
		return;
	}

	wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
	peer_mc_stats = peer_cp_stats_priv->peer_stats;
	ev = arg;
	ev->peer_stats[ev->num_peer_stats] = *peer_mc_stats;
	ev->num_peer_stats++;
	wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);
}

static void
tgt_mc_cp_stats_prepare_raw_peer_rssi(struct wlan_objmgr_psoc *psoc,
				      struct request_info *last_req)
{
	uint8_t *mac_addr;
	uint16_t peer_count;
	struct stats_event ev = {0};
	struct wlan_objmgr_pdev *pdev;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_peer *peer = NULL;
	struct peer_mc_cp_stats *peer_mc_stats;
	struct peer_cp_stats *peer_cp_stats_priv;
	void (*get_peer_rssi_cb)(struct stats_event *ev, void *cookie);

	get_peer_rssi_cb = last_req->u.get_peer_rssi_cb;
	if (!get_peer_rssi_cb) {
		cp_stats_err("get_peer_rssi_cb is null");
		return;
	}

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, last_req->vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev is null");
		goto end;
	}

	mac_addr = last_req->peer_mac_addr;
	if (QDF_IS_ADDR_BROADCAST(mac_addr)) {
		pdev = wlan_vdev_get_pdev(vdev);
		peer_count = wlan_pdev_get_peer_count(pdev);
		ev.peer_stats = qdf_mem_malloc(sizeof(*ev.peer_stats) *
								peer_count);
		if (!ev.peer_stats) {
			cp_stats_err("malloc failed");
			goto end;
		}

		wlan_objmgr_pdev_iterate_obj_list(pdev, WLAN_PEER_OP,
						  peer_rssi_iterator, &ev,
						  true, WLAN_CP_STATS_ID);
	} else {
		peer = wlan_objmgr_get_peer(psoc, last_req->pdev_id,
					    mac_addr, WLAN_CP_STATS_ID);
		if (!peer) {
			cp_stats_err("peer[%pM] is null", mac_addr);
			goto end;
		}

		peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
		if (!peer_cp_stats_priv) {
			cp_stats_err("peer cp stats object is null");
			goto end;
		}

		ev.peer_stats = qdf_mem_malloc(sizeof(*ev.peer_stats));
		if (!ev.peer_stats) {
			cp_stats_err("malloc failed");
			goto end;
		}

		ev.num_peer_stats = 1;
		wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
		peer_mc_stats = peer_cp_stats_priv->peer_stats;
		*ev.peer_stats = *peer_mc_stats;
		wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);
	}

end:
	if (ev.peer_stats)
		get_peer_rssi_cb(&ev, last_req->cookie);

	ucfg_mc_cp_stats_free_stats_resources(&ev);

	if (vdev)
		wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
	if (peer)
		wlan_objmgr_peer_release_ref(peer, WLAN_CP_STATS_ID);
}

static QDF_STATUS
tgt_mc_cp_stats_update_peer_adv_stats(struct wlan_objmgr_psoc *psoc,
				      struct peer_adv_mc_cp_stats
				      *peer_adv_stats, uint32_t size)
{
	uint8_t *peer_mac_addr;
	struct wlan_objmgr_peer *peer;
	struct peer_adv_mc_cp_stats *peer_adv_mc_stats;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct peer_cp_stats *peer_cp_stats_priv;

	if (!peer_adv_stats)
		return QDF_STATUS_E_INVAL;

	peer_mac_addr = peer_adv_stats->peer_macaddr;
	peer = wlan_objmgr_get_peer_by_mac(psoc, peer_mac_addr,
					   WLAN_CP_STATS_ID);
	if (!peer) {
		cp_stats_err("peer is null");
		return QDF_STATUS_E_EXISTS;
	}
	peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
	if (!peer_cp_stats_priv) {
		cp_stats_err("peer_cp_stats_priv is null");
		status = QDF_STATUS_E_EXISTS;
		goto end;
	}
	wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
	peer_adv_mc_stats = peer_cp_stats_priv->peer_adv_stats;

	qdf_mem_copy(peer_adv_mc_stats->peer_macaddr,
		     peer_adv_stats->peer_macaddr,
		     WLAN_MACADDR_LEN);
	if (peer_adv_stats->fcs_count)
		peer_adv_mc_stats->fcs_count = peer_adv_stats->fcs_count;
	if (peer_adv_stats->rx_bytes)
		peer_adv_mc_stats->rx_bytes = peer_adv_stats->rx_bytes;
	if (peer_adv_stats->rx_count)
		peer_adv_mc_stats->rx_count = peer_adv_stats->rx_count;
	wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);

end:
	if (peer)
		wlan_objmgr_peer_release_ref(peer, WLAN_CP_STATS_ID);

	return status;
}

static QDF_STATUS
tgt_mc_cp_stats_update_peer_stats(struct wlan_objmgr_psoc *psoc,
				  struct peer_mc_cp_stats *peer_stats)
{
	uint8_t *peer_mac_addr;
	struct wlan_objmgr_peer *peer;
	struct peer_mc_cp_stats *peer_mc_stats;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct peer_cp_stats *peer_cp_stats_priv;

	if (!peer_stats)
		return QDF_STATUS_E_INVAL;

	peer_mac_addr = peer_stats->peer_macaddr;
	peer = wlan_objmgr_get_peer_by_mac(psoc, peer_mac_addr,
				    WLAN_CP_STATS_ID);
	if (!peer) {
		cp_stats_err("peer is null");
		return QDF_STATUS_E_EXISTS;
	}

	peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
	if (!peer_cp_stats_priv) {
		cp_stats_err("peer_cp_stats_priv is null");
		status = QDF_STATUS_E_EXISTS;
		goto end;
	}

	wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
	peer_mc_stats = peer_cp_stats_priv->peer_stats;
	qdf_mem_copy(peer_mc_stats->peer_macaddr,
		     peer_stats->peer_macaddr,
		     WLAN_MACADDR_LEN);
	if (peer_stats->tx_rate)
		peer_mc_stats->tx_rate = peer_stats->tx_rate;
	if (peer_stats->rx_rate)
		peer_mc_stats->rx_rate = peer_stats->rx_rate;
	if (peer_stats->peer_rssi)
		peer_mc_stats->peer_rssi = peer_stats->peer_rssi;

	cp_stats_nofl_debug("PEER STATS: peer_mac=%pM, tx_rate=%u, rx_rate=%u, peer_rssi=%d",
			    peer_mc_stats->peer_macaddr, peer_mc_stats->tx_rate,
			    peer_mc_stats->rx_rate, peer_mc_stats->peer_rssi);
	wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);

end:
	if (peer)
		wlan_objmgr_peer_release_ref(peer, WLAN_CP_STATS_ID);

	return status;
}

static void tgt_mc_cp_stats_extract_peer_stats(struct wlan_objmgr_psoc *psoc,
					       struct stats_event *ev,
					       bool is_station_stats)
{
	uint32_t i;
	QDF_STATUS status;
	struct request_info last_req = {0};
	uint32_t selected;


	if (is_station_stats)
		status = ucfg_mc_cp_stats_get_pending_req(psoc,
							  TYPE_STATION_STATS,
							  &last_req);
	else
		status = ucfg_mc_cp_stats_get_pending_req(psoc,
							  TYPE_PEER_STATS,
							  &last_req);

	if (QDF_IS_STATUS_ERROR(status)) {
		cp_stats_err("ucfg_mc_cp_stats_get_pending_req failed");
		return;
	}

	if (!ev->peer_stats)
		goto extd2_stats;

	selected = ev->num_peer_stats;
	for (i = 0; i < ev->num_peer_stats; i++) {
		status = tgt_mc_cp_stats_update_peer_stats(psoc,
							   &ev->peer_stats[i]);
		if (!QDF_IS_ADDR_BROADCAST(last_req.peer_mac_addr) &&
		    !qdf_mem_cmp(ev->peer_stats[i].peer_macaddr,
				 last_req.peer_mac_addr,
				 WLAN_MACADDR_LEN)) {
			/* mac is specified, but failed to update the peer */
			if (QDF_IS_STATUS_ERROR(status))
				return;

			selected = i;
		}
	}

	/* no matched peer */
	if (!QDF_IS_ADDR_BROADCAST(last_req.peer_mac_addr) &&
	    selected == ev->num_peer_stats) {
		cp_stats_err("peer not found for stats");
	}

extd2_stats:

	if (!ev->peer_adv_stats)
		goto complete;

	selected = ev->num_peer_adv_stats;
	for (i = 0; i < ev->num_peer_adv_stats; i++) {
		status = tgt_mc_cp_stats_update_peer_adv_stats(
						psoc, &ev->peer_adv_stats[i],
						ev->num_peer_adv_stats);
		if (!QDF_IS_ADDR_BROADCAST(last_req.peer_mac_addr) &&
		    !qdf_mem_cmp(ev->peer_adv_stats[i].peer_macaddr,
				 last_req.peer_mac_addr,
				 WLAN_MACADDR_LEN)) {
			/* mac is specified, but failed to update the peer */
			if (QDF_IS_STATUS_ERROR(status))
				return;

			selected = i;
		}
	}

	/* no matched peer */
	if (!QDF_IS_ADDR_BROADCAST(last_req.peer_mac_addr) &&
	    selected == ev->num_peer_adv_stats) {
		cp_stats_err("peer not found for extd stats");
		return;
	}

complete:
	if (is_station_stats)
		return;

	if (tgt_mc_cp_stats_is_last_event(ev)) {
		ucfg_mc_cp_stats_reset_pending_req(psoc, TYPE_PEER_STATS);
		tgt_mc_cp_stats_prepare_raw_peer_rssi(psoc, &last_req);
	}
}

static void tgt_mc_cp_stats_extract_cca_stats(struct wlan_objmgr_psoc *psoc,
						  struct stats_event *ev)
{
	struct wlan_objmgr_vdev *vdev;
	struct vdev_mc_cp_stats *vdev_mc_stats;
	struct vdev_cp_stats *vdev_cp_stats_priv;

	if (!ev->cca_stats)
		return;

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc,
						    ev->cca_stats->vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev is null");
		return;
	}

	vdev_cp_stats_priv = wlan_cp_stats_get_vdev_stats_obj(vdev);
	if (!vdev_cp_stats_priv) {
		cp_stats_err("vdev cp stats object is null");
		goto end;
	}

	wlan_cp_stats_vdev_obj_lock(vdev_cp_stats_priv);
	vdev_mc_stats = vdev_cp_stats_priv->vdev_stats;
	vdev_mc_stats->cca.congestion =  ev->cca_stats->congestion;
	wlan_cp_stats_vdev_obj_unlock(vdev_cp_stats_priv);

end:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
}

static void tgt_mc_cp_stats_extract_vdev_summary_stats(
					struct wlan_objmgr_psoc *psoc,
					struct stats_event *ev)
{
	uint8_t i;
	QDF_STATUS status;
	struct wlan_objmgr_peer *peer = NULL;
	struct request_info last_req = {0};
	struct wlan_objmgr_vdev *vdev;
	struct peer_mc_cp_stats *peer_mc_stats;
	struct vdev_mc_cp_stats *vdev_mc_stats;
	struct peer_cp_stats *peer_cp_stats_priv;
	struct vdev_cp_stats *vdev_cp_stats_priv;

	if (!ev->vdev_summary_stats)
		return;

	status = ucfg_mc_cp_stats_get_pending_req(psoc,
						 TYPE_STATION_STATS,
						 &last_req);
	if (QDF_IS_STATUS_ERROR(status)) {
		cp_stats_err("ucfg_mc_cp_stats_get_pending_req failed");
		return;
	}

	for (i = 0; i < ev->num_summary_stats; i++) {
		if (ev->vdev_summary_stats[i].vdev_id == last_req.vdev_id)
			break;
	}

	if (i == ev->num_summary_stats) {
		cp_stats_debug("vdev_id %d not found", last_req.vdev_id);
		return;
	}

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, last_req.vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev is null");
		return;
	}

	vdev_cp_stats_priv = wlan_cp_stats_get_vdev_stats_obj(vdev);
	if (!vdev_cp_stats_priv) {
		cp_stats_err("vdev cp stats object is null");
		goto end;
	}

	wlan_cp_stats_vdev_obj_lock(vdev_cp_stats_priv);
	vdev_mc_stats = vdev_cp_stats_priv->vdev_stats;
	qdf_mem_copy(&vdev_mc_stats->vdev_summary_stats,
		     &ev->vdev_summary_stats[i].stats,
		     sizeof(vdev_mc_stats->vdev_summary_stats));
	wlan_cp_stats_vdev_obj_unlock(vdev_cp_stats_priv);

	peer = wlan_objmgr_get_peer(psoc, last_req.pdev_id,
				    last_req.peer_mac_addr, WLAN_CP_STATS_ID);
	if (!peer) {
		cp_stats_err("peer is null %pM", last_req.peer_mac_addr);
		goto end;
	}

	peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
	if (!peer_cp_stats_priv) {
		cp_stats_err("peer cp stats object is null");
		goto end;
	}

	wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
	peer_mc_stats = peer_cp_stats_priv->peer_stats;
	peer_mc_stats->peer_rssi = ev->vdev_summary_stats[i].stats.rssi;
	wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);

end:
	if (peer)
		wlan_objmgr_peer_release_ref(peer, WLAN_CP_STATS_ID);
	wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
}

static void tgt_mc_cp_stats_extract_vdev_chain_rssi_stats(
					struct wlan_objmgr_psoc *psoc,
					struct stats_event *ev)
{
	uint8_t i, j;
	QDF_STATUS status;
	struct request_info last_req = {0};
	struct wlan_objmgr_vdev *vdev;
	struct vdev_mc_cp_stats *vdev_mc_stats;
	struct vdev_cp_stats *vdev_cp_stats_priv;

	if (!ev->vdev_chain_rssi)
		return;

	status = ucfg_mc_cp_stats_get_pending_req(psoc,
						  TYPE_STATION_STATS,
						  &last_req);
	if (QDF_IS_STATUS_ERROR(status)) {
		cp_stats_err("ucfg_mc_cp_stats_get_pending_req failed");
		return;
	}

	for (i = 0; i < ev->num_chain_rssi_stats; i++) {
		if (ev->vdev_chain_rssi[i].vdev_id == last_req.vdev_id)
			break;
	}

	if (i == ev->num_chain_rssi_stats) {
		cp_stats_debug("vdev_id %d not found", last_req.vdev_id);
		return;
	}

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, last_req.vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev is null");
		return;
	}

	vdev_cp_stats_priv = wlan_cp_stats_get_vdev_stats_obj(vdev);
	if (!vdev_cp_stats_priv) {
		cp_stats_err("vdev cp stats object is null");
		goto end;
	}

	wlan_cp_stats_vdev_obj_lock(vdev_cp_stats_priv);
	vdev_mc_stats = vdev_cp_stats_priv->vdev_stats;
	for (j = 0; j < MAX_NUM_CHAINS; j++) {
		vdev_mc_stats->chain_rssi[j] =
					ev->vdev_chain_rssi[i].chain_rssi[j];
	}
	wlan_cp_stats_vdev_obj_unlock(vdev_cp_stats_priv);

end:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
}

static void
tgt_mc_cp_stats_prepare_n_send_raw_station_stats(struct wlan_objmgr_psoc *psoc,
						 struct request_info *last_req)
{
	/* station_stats to be given to userspace thread */
	struct stats_event info = {0};
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_peer *peer;
	struct peer_mc_cp_stats *peer_mc_stats;
	struct vdev_mc_cp_stats *vdev_mc_stats;
	struct peer_cp_stats *peer_cp_stats_priv;
	struct vdev_cp_stats *vdev_cp_stats_priv;
	void (*get_station_stats_cb)(struct stats_event *info, void *cookie);

	get_station_stats_cb = last_req->u.get_station_stats_cb;
	if (!get_station_stats_cb) {
		cp_stats_err("callback is null");
		return;
	}

	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, last_req->vdev_id,
						    WLAN_CP_STATS_ID);
	if (!vdev) {
		cp_stats_err("vdev object is null");
		return;
	}

	peer = wlan_objmgr_get_peer(psoc, last_req->pdev_id,
				    last_req->peer_mac_addr, WLAN_CP_STATS_ID);
	if (!peer) {
		cp_stats_err("peer object is null");
		goto end;
	}

	vdev_cp_stats_priv = wlan_cp_stats_get_vdev_stats_obj(vdev);
	if (!vdev_cp_stats_priv) {
		cp_stats_err("vdev cp stats object is null");
		goto end;
	}

	peer_cp_stats_priv = wlan_cp_stats_get_peer_stats_obj(peer);
	if (!peer_cp_stats_priv) {
		cp_stats_err("peer cp stats object is null");
		goto end;
	}

	info.num_summary_stats = 1;
	info.vdev_summary_stats = qdf_mem_malloc(
					sizeof(*info.vdev_summary_stats));
	if (!info.vdev_summary_stats)
		goto end;

	info.num_chain_rssi_stats = 1;
	info.vdev_chain_rssi = qdf_mem_malloc(sizeof(*info.vdev_chain_rssi));;
	if (!info.vdev_chain_rssi)
		goto end;

	wlan_cp_stats_vdev_obj_lock(vdev_cp_stats_priv);
	vdev_mc_stats = vdev_cp_stats_priv->vdev_stats;
	info.vdev_summary_stats[0].vdev_id = last_req->vdev_id;
	info.vdev_summary_stats[0].stats = vdev_mc_stats->vdev_summary_stats;
	info.vdev_chain_rssi[0].vdev_id = last_req->vdev_id;
	qdf_mem_copy(info.vdev_chain_rssi[0].chain_rssi,
		     vdev_mc_stats->chain_rssi,
		     sizeof(vdev_mc_stats->chain_rssi));
	info.tx_rate_flags = vdev_mc_stats->tx_rate_flags;
	wlan_cp_stats_vdev_obj_unlock(vdev_cp_stats_priv);

	info.peer_adv_stats = qdf_mem_malloc(sizeof(*info.peer_adv_stats));
	if (!info.peer_adv_stats)
		goto end;

	wlan_cp_stats_peer_obj_lock(peer_cp_stats_priv);
	peer_mc_stats = peer_cp_stats_priv->peer_stats;
	/*
	 * The linkspeed returned by fw is in kbps so convert
	 * it in units of 100kbps which is expected by UMAC
	 */
	info.tx_rate = peer_mc_stats->tx_rate / 100;
	info.rx_rate = peer_mc_stats->rx_rate / 100;

	if (peer_cp_stats_priv->peer_adv_stats) {
		info.num_peer_adv_stats = 1;
		qdf_mem_copy(info.peer_adv_stats,
			     peer_cp_stats_priv->peer_adv_stats,
			     sizeof(peer_cp_stats_priv->peer_adv_stats));
	}

	wlan_cp_stats_peer_obj_unlock(peer_cp_stats_priv);

end:
	if (info.vdev_summary_stats && info.vdev_chain_rssi)
		get_station_stats_cb(&info, last_req->cookie);

	ucfg_mc_cp_stats_free_stats_resources(&info);

	if (peer)
		wlan_objmgr_peer_release_ref(peer, WLAN_CP_STATS_ID);

	wlan_objmgr_vdev_release_ref(vdev, WLAN_CP_STATS_ID);
}

static void tgt_mc_cp_stats_extract_station_stats(
				struct wlan_objmgr_psoc *psoc,
				struct stats_event *ev)
{
	QDF_STATUS status;
	struct request_info last_req = {0};

	status = ucfg_mc_cp_stats_get_pending_req(psoc,
						  TYPE_STATION_STATS,
						  &last_req);
	if (QDF_IS_STATUS_ERROR(status)) {
		cp_stats_err("ucfg_mc_cp_stats_get_pending_req failed");
		return;
	}

	tgt_mc_cp_stats_extract_tx_power(psoc, ev, true);
	tgt_mc_cp_stats_extract_peer_stats(psoc, ev, true);
	tgt_mc_cp_stats_extract_vdev_summary_stats(psoc, ev);
	tgt_mc_cp_stats_extract_vdev_chain_rssi_stats(psoc, ev);

	/*
	 * PEER stats are the last stats sent for get_station statistics.
	 * reset type_map bit for station stats .
	 */
	if (tgt_mc_cp_stats_is_last_event(ev)) {
		ucfg_mc_cp_stats_reset_pending_req(psoc, TYPE_STATION_STATS);
		tgt_mc_cp_stats_prepare_n_send_raw_station_stats(psoc,
								 &last_req);
	}
}

static void tgt_mc_cp_send_lost_link_stats(struct wlan_objmgr_psoc *psoc,
					   struct stats_event *ev)
{
	struct psoc_cp_stats *psoc_cp_stats_priv;

	psoc_cp_stats_priv = wlan_cp_stats_get_psoc_stats_obj(psoc);
	if (psoc_cp_stats_priv && psoc_cp_stats_priv->legacy_stats_cb)
		psoc_cp_stats_priv->legacy_stats_cb(ev);
}

QDF_STATUS tgt_mc_cp_stats_process_stats_event(struct wlan_objmgr_psoc *psoc,
					       struct stats_event *ev)
{
	if (ucfg_mc_cp_stats_is_req_pending(psoc, TYPE_CONNECTION_TX_POWER))
		tgt_mc_cp_stats_extract_tx_power(psoc, ev, false);

	if (ucfg_mc_cp_stats_is_req_pending(psoc, TYPE_PEER_STATS))
		tgt_mc_cp_stats_extract_peer_stats(psoc, ev, false);

	if (ucfg_mc_cp_stats_is_req_pending(psoc, TYPE_STATION_STATS))
		tgt_mc_cp_stats_extract_station_stats(psoc, ev);

	tgt_mc_cp_stats_extract_cca_stats(psoc, ev);

	tgt_mc_cp_send_lost_link_stats(psoc, ev);
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tgt_mc_cp_stats_inc_wake_lock_stats(struct wlan_objmgr_psoc *psoc,
					       uint32_t reason,
					       struct wake_lock_stats *stats,
					       uint32_t *unspecified_wake_count)
{
	struct wlan_lmac_if_cp_stats_tx_ops *tx_ops;

	tx_ops = target_if_cp_stats_get_tx_ops(psoc);
	if (!tx_ops)
		return QDF_STATUS_E_NULL_VALUE;

	tx_ops->inc_wake_lock_stats(reason, stats, unspecified_wake_count);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tgt_send_mc_cp_stats_req(struct wlan_objmgr_psoc *psoc,
				    enum stats_req_type type,
				    struct request_info *req)
{
	struct wlan_lmac_if_cp_stats_tx_ops *tx_ops;

	tx_ops = target_if_cp_stats_get_tx_ops(psoc);
	if (!tx_ops || !tx_ops->send_req_stats) {
		cp_stats_err("could not get tx_ops");
		return QDF_STATUS_E_NULL_VALUE;
	}

	return tx_ops->send_req_stats(psoc, type, req);
}
